/*
 * HTMLtree.c : implementation of access function for an HTML tree.
 *
 * See Copyright for the status of this software.
 *
 * Author: Daniel Veillard
 */


#define IN_LIBXML
#include "libxml.h"
#ifdef LIBXML_HTML_ENABLED

#include <string.h> /* for memset() only ! */
#include <ctype.h>
#include <stdlib.h>

#include <libxml/xmlmemory.h>
#include <libxml/HTMLparser.h>
#include <libxml/HTMLtree.h>
#include <libxml/entities.h>
#include <libxml/xmlerror.h>
#include <libxml/parserInternals.h>
#include <libxml/uri.h>

#include "private/buf.h"
#include "private/error.h"
#include "private/io.h"
#include "private/save.h"

/************************************************************************
 *									*
 *		Getting/Setting encoding meta tags			*
 *									*
 ************************************************************************/

/**
 * Look up and encoding declaration in the meta tags.
 *
 * Does not support `<meta charset="">` yet. Only supports deprecated
 * `<meta http-equiv="Content-Type" content="">`.
 *
 * The returned string points into attribute content. It should be
 * copied before modifying or freeing nodes.
 *
 * @param doc  the document
 * @returns the encoding ot NULL if not found.
 */
const xmlChar *
htmlGetMetaEncoding(htmlDocPtr doc) {
    htmlNodePtr cur;
    const xmlChar *content;
    const xmlChar *encoding;

    if (doc == NULL)
	return(NULL);
    cur = doc->children;

    /*
     * Search the html
     */
    while (cur != NULL) {
	if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
	    if (xmlStrEqual(cur->name, BAD_CAST"html"))
		break;
	    if (xmlStrEqual(cur->name, BAD_CAST"head"))
		goto found_head;
	    if (xmlStrEqual(cur->name, BAD_CAST"meta"))
		goto found_meta;
	}
	cur = cur->next;
    }
    if (cur == NULL)
	return(NULL);
    cur = cur->children;

    /*
     * Search the head
     */
    while (cur != NULL) {
	if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
	    if (xmlStrEqual(cur->name, BAD_CAST"head"))
		break;
	    if (xmlStrEqual(cur->name, BAD_CAST"meta"))
		goto found_meta;
	}
	cur = cur->next;
    }
    if (cur == NULL)
	return(NULL);
found_head:
    cur = cur->children;

    /*
     * Search the meta elements
     */
found_meta:
    while (cur != NULL) {
	if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
	    if (xmlStrEqual(cur->name, BAD_CAST"meta")) {
		xmlAttrPtr attr = cur->properties;
		int http;
		const xmlChar *value;

		content = NULL;
		http = 0;
		while (attr != NULL) {
		    if ((attr->children != NULL) &&
		        (attr->children->type == XML_TEXT_NODE) &&
		        (attr->children->next == NULL)) {
			value = attr->children->content;
			if ((!xmlStrcasecmp(attr->name, BAD_CAST"http-equiv"))
			 && (!xmlStrcasecmp(value, BAD_CAST"Content-Type")))
			    http = 1;
			else if ((value != NULL)
			 && (!xmlStrcasecmp(attr->name, BAD_CAST"content")))
			    content = value;
			if ((http != 0) && (content != NULL))
			    goto found_content;
		    }
		    attr = attr->next;
		}
	    }
	}
	cur = cur->next;
    }
    return(NULL);

found_content:
    encoding = xmlStrstr(content, BAD_CAST"charset=");
    if (encoding == NULL)
	encoding = xmlStrstr(content, BAD_CAST"Charset=");
    if (encoding == NULL)
	encoding = xmlStrstr(content, BAD_CAST"CHARSET=");
    if (encoding != NULL) {
	encoding += 8;
    } else {
	encoding = xmlStrstr(content, BAD_CAST"charset =");
	if (encoding == NULL)
	    encoding = xmlStrstr(content, BAD_CAST"Charset =");
	if (encoding == NULL)
	    encoding = xmlStrstr(content, BAD_CAST"CHARSET =");
	if (encoding != NULL)
	    encoding += 9;
    }
    if (encoding != NULL) {
	while ((*encoding == ' ') || (*encoding == '\t')) encoding++;
    }
    return(encoding);
}

/**
 * Creates or updates a meta tag with an encoding declaration.
 *
 * Does not support `<meta charset="">` yet. Only supports deprecated
 * `<meta http-equiv="Content-Type" content="">`.
 *
 * NOTE: This will not change the document content encoding.
 *
 * @param doc  the document
 * @param encoding  the encoding string
 * @returns 0 in case of success and -1 in case of error
 */
int
htmlSetMetaEncoding(htmlDocPtr doc, const xmlChar *encoding) {
    htmlNodePtr cur, meta = NULL, head = NULL;
    const xmlChar *content = NULL;
    char newcontent[100];

    newcontent[0] = 0;

    if (doc == NULL)
	return(-1);

    /* html isn't a real encoding it's just libxml2 way to get entities */
    if (!xmlStrcasecmp(encoding, BAD_CAST "html"))
        return(-1);

    if (encoding != NULL) {
	snprintf(newcontent, sizeof(newcontent), "text/html; charset=%s",
                (char *)encoding);
	newcontent[sizeof(newcontent) - 1] = 0;
    }

    cur = doc->children;

    /*
     * Search the html
     */
    while (cur != NULL) {
	if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
	    if (xmlStrcasecmp(cur->name, BAD_CAST"html") == 0)
		break;
	    if (xmlStrcasecmp(cur->name, BAD_CAST"head") == 0)
		goto found_head;
	    if (xmlStrcasecmp(cur->name, BAD_CAST"meta") == 0)
		goto found_meta;
	}
	cur = cur->next;
    }
    if (cur == NULL)
	return(-1);
    cur = cur->children;

    /*
     * Search the head
     */
    while (cur != NULL) {
	if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
	    if (xmlStrcasecmp(cur->name, BAD_CAST"head") == 0)
		break;
	    if (xmlStrcasecmp(cur->name, BAD_CAST"meta") == 0) {
                head = cur->parent;
		goto found_meta;
            }
	}
	cur = cur->next;
    }
    if (cur == NULL)
	return(-1);
found_head:
    head = cur;
    if (cur->children == NULL)
        goto create;
    cur = cur->children;

found_meta:
    /*
     * Search and update all the remaining the meta elements carrying
     * encoding information
     */
    while (cur != NULL) {
	if ((cur->type == XML_ELEMENT_NODE) && (cur->name != NULL)) {
	    if (xmlStrcasecmp(cur->name, BAD_CAST"meta") == 0) {
		xmlAttrPtr attr = cur->properties;
		int http;
		const xmlChar *value;

		content = NULL;
		http = 0;
		while (attr != NULL) {
		    if ((attr->children != NULL) &&
		        (attr->children->type == XML_TEXT_NODE) &&
		        (attr->children->next == NULL)) {
			value = attr->children->content;
			if ((!xmlStrcasecmp(attr->name, BAD_CAST"http-equiv"))
			 && (!xmlStrcasecmp(value, BAD_CAST"Content-Type")))
			    http = 1;
			else
                        {
                           if ((value != NULL) &&
                               (!xmlStrcasecmp(attr->name, BAD_CAST"content")))
			       content = value;
                        }
		        if ((http != 0) && (content != NULL))
			    break;
		    }
		    attr = attr->next;
		}
		if ((http != 0) && (content != NULL)) {
		    meta = cur;
		    break;
		}

	    }
	}
	cur = cur->next;
    }
create:
    if (meta == NULL) {
        if ((encoding != NULL) && (head != NULL)) {
            /*
             * Create a new Meta element with the right attributes
             */

            meta = xmlNewDocNode(doc, NULL, BAD_CAST"meta", NULL);
            if (head->children == NULL)
                xmlAddChild(head, meta);
            else
                xmlAddPrevSibling(head->children, meta);
            xmlNewProp(meta, BAD_CAST"http-equiv", BAD_CAST"Content-Type");
            xmlNewProp(meta, BAD_CAST"content", BAD_CAST newcontent);
        }
    } else {
        /* remove the meta tag if NULL is passed */
        if (encoding == NULL) {
            xmlUnlinkNode(meta);
            xmlFreeNode(meta);
        }
        /* change the document only if there is a real encoding change */
        else if (xmlStrcasestr(content, encoding) == NULL) {
            xmlSetProp(meta, BAD_CAST"content", BAD_CAST newcontent);
        }
    }


    return(0);
}

/**
 * These are the HTML attributes which will be output
 * in minimized form, i.e. `<option selected="selected">` will be
 * output as `<option selected>`, as per XSLT 1.0 16.2 "HTML Output Method"
 */
static const char* const htmlBooleanAttrs[] = {
  "checked", "compact", "declare", "defer", "disabled", "ismap",
  "multiple", "nohref", "noresize", "noshade", "nowrap", "readonly",
  "selected", NULL
};


/**
 * Determine if a given attribute is a boolean attribute. This
 * doesn't handle HTML5.
 *
 * @deprecated Internal function, don't use.
 *
 * @param name  the name of the attribute to check
 * @returns false if the attribute is not boolean, true otherwise.
 */
int
htmlIsBooleanAttr(const xmlChar *name)
{
    int i = 0;

    while (htmlBooleanAttrs[i] != NULL) {
        if (xmlStrcasecmp((const xmlChar *)htmlBooleanAttrs[i], name) == 0)
            return 1;
        i++;
    }
    return 0;
}

#ifdef LIBXML_OUTPUT_ENABLED
/************************************************************************
 *									*
 *		Dumping HTML tree content to a simple buffer		*
 *									*
 ************************************************************************/

static xmlParserErrors
htmlFindOutputEncoder(const char *encoding, xmlCharEncodingHandler **out) {
    /*
     * Fallback to HTML if the encoding is unspecified
     */
    if (encoding == NULL)
        encoding = "HTML";

    return(xmlOpenCharEncodingHandler(encoding, /* output */ 1, out));
}

/**
 * Serialize an HTML document to an xmlBuf.
 *
 * @param buf  the xmlBufPtr output
 * @param doc  the document
 * @param cur  the current node
 * @param format  should formatting newlines been added
 * @returns the number of bytes written or -1 in case of error
 */
static size_t
htmlBufNodeDumpFormat(xmlBufPtr buf, xmlDocPtr doc, xmlNodePtr cur,
	           int format) {
    size_t use;
    size_t ret;
    xmlOutputBufferPtr outbuf;

    if (cur == NULL) {
	return ((size_t) -1);
    }
    if (buf == NULL) {
	return ((size_t) -1);
    }
    outbuf = (xmlOutputBufferPtr) xmlMalloc(sizeof(xmlOutputBuffer));
    if (outbuf == NULL)
	return ((size_t) -1);
    memset(outbuf, 0, sizeof(xmlOutputBuffer));
    outbuf->buffer = buf;
    outbuf->encoder = NULL;
    outbuf->writecallback = NULL;
    outbuf->closecallback = NULL;
    outbuf->context = NULL;
    outbuf->written = 0;

    use = xmlBufUse(buf);
    htmlNodeDumpFormatOutput(outbuf, doc, cur, NULL, format);
    if (outbuf->error)
        ret = (size_t) -1;
    else
        ret = xmlBufUse(buf) - use;
    xmlFree(outbuf);
    return (ret);
}

/**
 * Serialize an HTML node to an xmlBuffer. Always uses UTF-8.
 *
 * @param buf  the HTML buffer output
 * @param doc  the document
 * @param cur  the current node
 * @returns the number of bytes written or -1 in case of error
 */
int
htmlNodeDump(xmlBufferPtr buf, xmlDocPtr doc, xmlNodePtr cur) {
    xmlBufPtr buffer;
    size_t ret1;
    int ret2;

    if ((buf == NULL) || (cur == NULL))
        return(-1);

    xmlInitParser();
    buffer = xmlBufFromBuffer(buf);
    if (buffer == NULL)
        return(-1);

    ret1 = htmlBufNodeDumpFormat(buffer, doc, cur, 1);

    ret2 = xmlBufBackToBuffer(buffer, buf);

    if ((ret1 == (size_t) -1) || (ret2 < 0))
        return(-1);
    return(ret1 > INT_MAX ? INT_MAX : ret1);
}

/**
 * Serialize an HTML node to an xmlBuffer.
 *
 * If encoding is NULL, ASCII with HTML 4.0 named character entities
 * will be used. This is inefficient compared to UTF-8 and might be
 * changed in a future version.
 *
 * @param out  the FILE pointer
 * @param doc  the document
 * @param cur  the current node
 * @param encoding  the document encoding (optional)
 * @param format  should formatting newlines been added
 * @returns the number of bytes written or -1 in case of failure.
 */
int
htmlNodeDumpFileFormat(FILE *out, xmlDocPtr doc,
	               xmlNodePtr cur, const char *encoding, int format) {
    xmlOutputBufferPtr buf;
    xmlCharEncodingHandlerPtr handler;
    int ret;

    xmlInitParser();

    /*
     * save the content to a temp buffer.
     */
    if (htmlFindOutputEncoder(encoding, &handler) != XML_ERR_OK)
        return(-1);
    buf = xmlOutputBufferCreateFile(out, handler);
    if (buf == NULL)
        return(-1);

    htmlNodeDumpFormatOutput(buf, doc, cur, NULL, format);

    ret = xmlOutputBufferClose(buf);
    return(ret);
}

/**
 * Same as htmlNodeDumpFileFormat() with `format` set to 1 which is
 * typically undesired. Use of this function is DISCOURAGED in favor
 * of htmlNodeDumpFileFormat().
 *
 * @param out  the FILE pointer
 * @param doc  the document
 * @param cur  the current node
 */
void
htmlNodeDumpFile(FILE *out, xmlDocPtr doc, xmlNodePtr cur) {
    htmlNodeDumpFileFormat(out, doc, cur, NULL, 1);
}

/**
 * Serialize an HTML node to a memory, also returning the size of
 * the result. It's up to the caller to free the memory.
 *
 * WARNING: Uses the encoding from a deprecated meta tag, see
 * htmlGetMetaEncoding(). This is typically undesired. If no such
 * tag was found, ASCII with HTML 4.0 named character entities will
 * be used. This is inefficient compared to UTF-8 and might be
 * changed in a future version.
 *
 * Use of this function is therefore DISCOURAGED in favor of
 * htmlDocContentDumpFormatOutput().
 * @param cur  the document
 * @param mem  OUT: the memory pointer
 * @param size  OUT: the memory length
 * @param format  should formatting newlines been added
 */
void
htmlDocDumpMemoryFormat(xmlDocPtr cur, xmlChar**mem, int *size, int format) {
    xmlOutputBufferPtr buf;
    xmlCharEncodingHandlerPtr handler = NULL;
    const char *encoding;

    xmlInitParser();

    if ((mem == NULL) || (size == NULL))
        return;
    *mem = NULL;
    *size = 0;
    if (cur == NULL)
	return;

    encoding = (const char *) htmlGetMetaEncoding(cur);
    if (htmlFindOutputEncoder(encoding, &handler) != XML_ERR_OK)
        return;
    buf = xmlAllocOutputBuffer(handler);
    if (buf == NULL)
	return;

    htmlDocContentDumpFormatOutput(buf, cur, NULL, format);

    xmlOutputBufferFlush(buf);

    if (!buf->error) {
        if (buf->conv != NULL) {
            *size = xmlBufUse(buf->conv);
            *mem = xmlStrndup(xmlBufContent(buf->conv), *size);
        } else {
            *size = xmlBufUse(buf->buffer);
            *mem = xmlStrndup(xmlBufContent(buf->buffer), *size);
        }
    }

    xmlOutputBufferClose(buf);
}

/**
 * Same as htmlDocDumpMemoryFormat() with `format` set to 1 which
 * is typically undesired. Also see the warnings there. Use of
 * this function is DISCOURAGED in favor of
 * htmlDocContentDumpFormatOutput().
 *
 * @param cur  the document
 * @param mem  OUT: the memory pointer
 * @param size  OUT: the memory length
 */
void
htmlDocDumpMemory(xmlDocPtr cur, xmlChar**mem, int *size) {
    htmlDocDumpMemoryFormat(cur, mem, size, 1);
}


/************************************************************************
 *									*
 *		Dumping HTML tree content to an I/O output buffer	*
 *									*
 ************************************************************************/

/**
 * Serialize the HTML document's DTD, if any.
 *
 * Ignores `encoding` and uses the encoding of the output buffer.
 * @param buf  the HTML buffer output
 * @param doc  the document
 * @param encoding  the encoding string (unused)
 */
static void
htmlDtdDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc,
	          const char *encoding ATTRIBUTE_UNUSED) {
    xmlDtdPtr cur = doc->intSubset;

    if (cur == NULL)
	return;
    xmlOutputBufferWriteString(buf, "<!DOCTYPE ");
    xmlOutputBufferWriteString(buf, (const char *)cur->name);
    if (cur->ExternalID != NULL) {
	xmlOutputBufferWriteString(buf, " PUBLIC ");
	xmlOutputBufferWriteQuotedString(buf, cur->ExternalID);
	if (cur->SystemID != NULL) {
	    xmlOutputBufferWriteString(buf, " ");
	    xmlOutputBufferWriteQuotedString(buf, cur->SystemID);
	}
    } else if (cur->SystemID != NULL &&
	       xmlStrcmp(cur->SystemID, BAD_CAST "about:legacy-compat")) {
	xmlOutputBufferWriteString(buf, " SYSTEM ");
	xmlOutputBufferWriteQuotedString(buf, cur->SystemID);
    }
    xmlOutputBufferWriteString(buf, ">\n");
}

/**
 * Serialize an HTML attribute.
 *
 * @param buf  the HTML buffer output
 * @param doc  the document
 * @param cur  the attribute pointer
 */
static void
htmlAttrDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc, xmlAttrPtr cur) {
    xmlChar *value;

    /*
     * The html output method should not escape a & character
     * occurring in an attribute value immediately followed by
     * a { character (see Section B.7.1 of the HTML 4.0 Recommendation).
     * This is implemented in xmlEncodeEntitiesReentrant
     */

    if (cur == NULL) {
	return;
    }
    xmlOutputBufferWriteString(buf, " ");
    if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) {
        xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix);
	xmlOutputBufferWriteString(buf, ":");
    }
    xmlOutputBufferWriteString(buf, (const char *)cur->name);
    if ((cur->children != NULL) && (!htmlIsBooleanAttr(cur->name))) {
	value = xmlNodeListGetString(doc, cur->children, 0);
	if (value) {
	    xmlOutputBufferWriteString(buf, "=");
	    if ((cur->ns == NULL) && (cur->parent != NULL) &&
		(cur->parent->ns == NULL) &&
		((!xmlStrcasecmp(cur->name, BAD_CAST "href")) ||
	         (!xmlStrcasecmp(cur->name, BAD_CAST "action")) ||
		 (!xmlStrcasecmp(cur->name, BAD_CAST "src")) ||
		 ((!xmlStrcasecmp(cur->name, BAD_CAST "name")) &&
		  (!xmlStrcasecmp(cur->parent->name, BAD_CAST "a"))))) {
		xmlChar *escaped;
		xmlChar *tmp = value;

		while (IS_BLANK_CH(*tmp)) tmp++;

		/*
                 * Angle brackets are technically illegal in URIs, but they're
                 * used in server side includes, for example. Curly brackets
                 * are illegal as well and often used in templates.
                 * Don't escape non-whitespace, printable ASCII chars for
                 * improved interoperability. Only escape space, control
                 * and non-ASCII chars.
		 */
		escaped = xmlURIEscapeStr(tmp,
                        BAD_CAST "\"#$%&+,/:;<=>?@[\\]^`{|}");
		if (escaped != NULL) {
		    xmlOutputBufferWriteQuotedString(buf, escaped);
		    xmlFree(escaped);
		} else {
                    buf->error = XML_ERR_NO_MEMORY;
		}
	    } else {
		xmlOutputBufferWriteQuotedString(buf, value);
	    }
	    xmlFree(value);
	} else  {
            buf->error = XML_ERR_NO_MEMORY;
	}
    }
}

/**
 * Serialize an HTML node to an output buffer.
 *
 * Ignores `encoding` and uses the encoding of the output buffer.
 * @param buf  the HTML buffer output
 * @param doc  the document
 * @param cur  the current node
 * @param encoding  the encoding string (unused)
 * @param format  should formatting newlines been added
 */
void
htmlNodeDumpFormatOutput(xmlOutputBufferPtr buf, xmlDocPtr doc,
	                 xmlNodePtr cur, const char *encoding ATTRIBUTE_UNUSED,
                         int format) {
    xmlNodePtr root, parent;
    xmlAttrPtr attr;
    const htmlElemDesc * info;

    xmlInitParser();

    if ((cur == NULL) || (buf == NULL)) {
	return;
    }

    root = cur;
    parent = cur->parent;
    while (1) {
        switch (cur->type) {
        case XML_HTML_DOCUMENT_NODE:
        case XML_DOCUMENT_NODE:
            if (((xmlDocPtr) cur)->intSubset != NULL) {
                htmlDtdDumpOutput(buf, (xmlDocPtr) cur, NULL);
            }
            if (cur->children != NULL) {
                /* Always validate cur->parent when descending. */
                if (cur->parent == parent) {
                    parent = cur;
                    cur = cur->children;
                    continue;
                }
            } else {
                xmlOutputBufferWriteString(buf, "\n");
            }
            break;

        case XML_ELEMENT_NODE:
            /*
             * Some users like lxml are known to pass nodes with a corrupted
             * tree structure. Fall back to a recursive call to handle this
             * case.
             */
            if ((cur->parent != parent) && (cur->children != NULL)) {
                htmlNodeDumpFormatOutput(buf, doc, cur, encoding, format);
                break;
            }

            /*
             * Get specific HTML info for that node.
             */
            if (cur->ns == NULL)
                info = htmlTagLookup(cur->name);
            else
                info = NULL;

            xmlOutputBufferWriteString(buf, "<");
            if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) {
                xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix);
                xmlOutputBufferWriteString(buf, ":");
            }
            xmlOutputBufferWriteString(buf, (const char *)cur->name);
            if (cur->nsDef)
                xmlNsListDumpOutput(buf, cur->nsDef);
            attr = cur->properties;
            while (attr != NULL) {
                htmlAttrDumpOutput(buf, doc, attr);
                attr = attr->next;
            }

            if ((info != NULL) && (info->empty)) {
                xmlOutputBufferWriteString(buf, ">");
            } else if (cur->children == NULL) {
                if ((info != NULL) && (info->saveEndTag != 0) &&
                    (xmlStrcmp(BAD_CAST info->name, BAD_CAST "html")) &&
                    (xmlStrcmp(BAD_CAST info->name, BAD_CAST "body"))) {
                    xmlOutputBufferWriteString(buf, ">");
                } else {
                    xmlOutputBufferWriteString(buf, "></");
                    if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) {
                        xmlOutputBufferWriteString(buf,
                                (const char *)cur->ns->prefix);
                        xmlOutputBufferWriteString(buf, ":");
                    }
                    xmlOutputBufferWriteString(buf, (const char *)cur->name);
                    xmlOutputBufferWriteString(buf, ">");
                }
            } else {
                xmlOutputBufferWriteString(buf, ">");
                if ((format) && (info != NULL) && (!info->isinline) &&
                    (cur->children->type != HTML_TEXT_NODE) &&
                    (cur->children->type != HTML_ENTITY_REF_NODE) &&
                    (cur->children != cur->last) &&
                    (cur->name != NULL) &&
                    (cur->name[0] != 'p')) /* p, pre, param */
                    xmlOutputBufferWriteString(buf, "\n");
                parent = cur;
                cur = cur->children;
                continue;
            }

            if ((format) && (cur->next != NULL) &&
                (info != NULL) && (!info->isinline)) {
                if ((cur->next->type != HTML_TEXT_NODE) &&
                    (cur->next->type != HTML_ENTITY_REF_NODE) &&
                    (parent != NULL) &&
                    (parent->name != NULL) &&
                    (parent->name[0] != 'p')) /* p, pre, param */
                    xmlOutputBufferWriteString(buf, "\n");
            }

            break;

        case XML_ATTRIBUTE_NODE:
            htmlAttrDumpOutput(buf, doc, (xmlAttrPtr) cur);
            break;

        case HTML_TEXT_NODE:
            if (cur->content == NULL)
                break;
            if (((cur->name == (const xmlChar *)xmlStringText) ||
                 (cur->name != (const xmlChar *)xmlStringTextNoenc)) &&
                ((parent == NULL) ||
                 ((xmlStrcasecmp(parent->name, BAD_CAST "script")) &&
                  (xmlStrcasecmp(parent->name, BAD_CAST "style"))))) {
                xmlChar *buffer;

                buffer = xmlEncodeEntitiesReentrant(doc, cur->content);
                if (buffer == NULL) {
                    buf->error = XML_ERR_NO_MEMORY;
                    return;
                }
                xmlOutputBufferWriteString(buf, (const char *)buffer);
                xmlFree(buffer);
            } else {
                xmlOutputBufferWriteString(buf, (const char *)cur->content);
            }
            break;

        case HTML_COMMENT_NODE:
            if (cur->content != NULL) {
                xmlOutputBufferWriteString(buf, "<!--");
                xmlOutputBufferWriteString(buf, (const char *)cur->content);
                xmlOutputBufferWriteString(buf, "-->");
            }
            break;

        case HTML_PI_NODE:
            if (cur->name != NULL) {
                xmlOutputBufferWriteString(buf, "<?");
                xmlOutputBufferWriteString(buf, (const char *)cur->name);
                if (cur->content != NULL) {
                    xmlOutputBufferWriteString(buf, " ");
                    xmlOutputBufferWriteString(buf,
                            (const char *)cur->content);
                }
                xmlOutputBufferWriteString(buf, ">");
            }
            break;

        case HTML_ENTITY_REF_NODE:
            xmlOutputBufferWriteString(buf, "&");
            xmlOutputBufferWriteString(buf, (const char *)cur->name);
            xmlOutputBufferWriteString(buf, ";");
            break;

        case HTML_PRESERVE_NODE:
            if (cur->content != NULL) {
                xmlOutputBufferWriteString(buf, (const char *)cur->content);
            }
            break;

        default:
            break;
        }

        while (1) {
            if (cur == root)
                return;
            if (cur->next != NULL) {
                cur = cur->next;
                break;
            }

            cur = parent;
            /* cur->parent was validated when descending. */
            parent = cur->parent;

            if ((cur->type == XML_HTML_DOCUMENT_NODE) ||
                (cur->type == XML_DOCUMENT_NODE)) {
                xmlOutputBufferWriteString(buf, "\n");
            } else {
                if ((format) && (cur->ns == NULL))
                    info = htmlTagLookup(cur->name);
                else
                    info = NULL;

                if ((format) && (info != NULL) && (!info->isinline) &&
                    (cur->last->type != HTML_TEXT_NODE) &&
                    (cur->last->type != HTML_ENTITY_REF_NODE) &&
                    (cur->children != cur->last) &&
                    (cur->name != NULL) &&
                    (cur->name[0] != 'p')) /* p, pre, param */
                    xmlOutputBufferWriteString(buf, "\n");

                xmlOutputBufferWriteString(buf, "</");
                if ((cur->ns != NULL) && (cur->ns->prefix != NULL)) {
                    xmlOutputBufferWriteString(buf, (const char *)cur->ns->prefix);
                    xmlOutputBufferWriteString(buf, ":");
                }
                xmlOutputBufferWriteString(buf, (const char *)cur->name);
                xmlOutputBufferWriteString(buf, ">");

                if ((format) && (info != NULL) && (!info->isinline) &&
                    (cur->next != NULL)) {
                    if ((cur->next->type != HTML_TEXT_NODE) &&
                        (cur->next->type != HTML_ENTITY_REF_NODE) &&
                        (parent != NULL) &&
                        (parent->name != NULL) &&
                        (parent->name[0] != 'p')) /* p, pre, param */
                        xmlOutputBufferWriteString(buf, "\n");
                }
            }
        }
    }
}

/**
 * Same as htmlNodeDumpFormatOutput() with `format` set to 1 which is
 * typically undesired. Use of this function is DISCOURAGED in favor
 * of htmlNodeDumpFormatOutput().
 *
 * Ignores `encoding` and uses the encoding of the output buffer.
 * @param buf  the HTML buffer output
 * @param doc  the document
 * @param cur  the current node
 * @param encoding  the encoding string (unused)
 */
void
htmlNodeDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr doc,
	           xmlNodePtr cur, const char *encoding ATTRIBUTE_UNUSED) {
    htmlNodeDumpFormatOutput(buf, doc, cur, NULL, 1);
}

/**
 * Serialize an HTML document to an output buffer.
 *
 * Ignores `encoding` and uses the encoding of the output buffer.
 * @param buf  the HTML buffer output
 * @param cur  the document
 * @param encoding  the encoding string (unused)
 * @param format  should formatting newlines been added
 */
void
htmlDocContentDumpFormatOutput(xmlOutputBufferPtr buf, xmlDocPtr cur,
	                       const char *encoding ATTRIBUTE_UNUSED,
                               int format) {
    int type = 0;
    if (cur) {
        type = cur->type;
        cur->type = XML_HTML_DOCUMENT_NODE;
    }
    htmlNodeDumpFormatOutput(buf, cur, (xmlNodePtr) cur, NULL, format);
    if (cur)
        cur->type = (xmlElementType) type;
}

/**
 * Same as htmlNodeDumpFormatOutput() with `format` set to 1 which is
 * typically undesired. Use of this function is DISCOURAGED in favor
 * of htmlDocContentDumpFormatOutput().
 *
 * Ignores `encoding` and uses the encoding of the output buffer.
 * @param buf  the HTML buffer output
 * @param cur  the document
 * @param encoding  the encoding string (unused)
 */
void
htmlDocContentDumpOutput(xmlOutputBufferPtr buf, xmlDocPtr cur,
	                 const char *encoding ATTRIBUTE_UNUSED) {
    htmlNodeDumpFormatOutput(buf, cur, (xmlNodePtr) cur, NULL, 1);
}

/************************************************************************
 *									*
 *		Saving functions front-ends				*
 *									*
 ************************************************************************/

/**
 * Serialize an HTML document to an open `FILE`.
 *
 * WARNING: Uses the encoding from a deprecated meta tag, see
 * htmlGetMetaEncoding(). This is typically undesired. If no such
 * tag was found, ASCII with HTML 4.0 named character entities will
 * be used. This is inefficient compared to UTF-8 and might be
 * changed in a future version.
 *
 * Also enables "formatting" unconditionally which is typically
 * undesired.
 *
 * Use of this function is DISCOURAGED in favor of
 * htmlNodeDumpFileFormat().
 *
 * @param f  the FILE*
 * @param cur  the document
 * @returns the number of bytes written or -1 in case of failure.
 */
int
htmlDocDump(FILE *f, xmlDocPtr cur) {
    xmlOutputBufferPtr buf;
    xmlCharEncodingHandlerPtr handler = NULL;
    const char *encoding;
    int ret;

    xmlInitParser();

    if ((cur == NULL) || (f == NULL)) {
	return(-1);
    }

    encoding = (const char *) htmlGetMetaEncoding(cur);
    if (htmlFindOutputEncoder(encoding, &handler) != XML_ERR_OK)
        return(-1);
    buf = xmlOutputBufferCreateFile(f, handler);
    if (buf == NULL)
        return(-1);
    htmlDocContentDumpOutput(buf, cur, NULL);

    ret = xmlOutputBufferClose(buf);
    return(ret);
}

/**
 * Serialize an HTML document to a file. If `filename` is `"-"`,
 * stdout is used. This is potentially insecure and might be
 * changed in a future version.
 *
 * WARNING: Uses the encoding from a deprecated meta tag, see
 * htmlGetMetaEncoding(). This is typically undesired. If no such
 * tag was found, ASCII with HTML 4.0 named character entities will
 * be used. This is inefficient compared to UTF-8 and might be
 * changed in a future version.
 *
 * Also enables "formatting" unconditionally which is typically
 * undesired.
 *
 * Use of this function is DISCOURAGED in favor of
 * htmlSaveFileFormat().
 *
 * @param filename  the filename (or URL)
 * @param cur  the document
 * @returns the number of bytes written or -1 in case of failure.
 */
int
htmlSaveFile(const char *filename, xmlDocPtr cur) {
    xmlOutputBufferPtr buf;
    xmlCharEncodingHandlerPtr handler = NULL;
    const char *encoding;
    int ret;

    if ((cur == NULL) || (filename == NULL))
        return(-1);

    xmlInitParser();

    encoding = (const char *) htmlGetMetaEncoding(cur);
    if (htmlFindOutputEncoder(encoding, &handler) != XML_ERR_OK)
        return(-1);
    buf = xmlOutputBufferCreateFilename(filename, handler, cur->compression);
    if (buf == NULL)
        return(-1);

    htmlDocContentDumpOutput(buf, cur, NULL);

    ret = xmlOutputBufferClose(buf);
    return(ret);
}

/**
 * Serialize an HTML document to a file using a given encoding.
 * If `filename` is `"-"`, stdout is used. This is potentially
 * insecure and might be changed in a future version.
 *
 * If encoding is NULL, ASCII with HTML 4.0 named character entities
 * will be used. This is inefficient compared to UTF-8 and might be
 * changed in a future version.
 *
 * @param filename  the filename
 * @param cur  the document
 * @param format  should formatting newlines been added
 * @param encoding  the document encoding (optional)
 * @returns the number of bytes written or -1 in case of failure.
 */
int
htmlSaveFileFormat(const char *filename, xmlDocPtr cur,
	           const char *encoding, int format) {
    xmlOutputBufferPtr buf;
    xmlCharEncodingHandlerPtr handler = NULL;
    int ret;

    if ((cur == NULL) || (filename == NULL))
        return(-1);

    xmlInitParser();

    if (htmlFindOutputEncoder(encoding, &handler) != XML_ERR_OK)
        return(-1);
    if (handler != NULL)
        htmlSetMetaEncoding(cur, (const xmlChar *) handler->name);
    else
	htmlSetMetaEncoding(cur, (const xmlChar *) "UTF-8");

    /*
     * save the content to a temp buffer.
     */
    buf = xmlOutputBufferCreateFilename(filename, handler, 0);
    if (buf == NULL)
        return(0);

    htmlDocContentDumpFormatOutput(buf, cur, encoding, format);

    ret = xmlOutputBufferClose(buf);
    return(ret);
}

/**
 * Same as htmlSaveFileFormat() with `format` set to 1 which is
 * typically undesired. Also see the warnings there. Use of this
 * function is DISCOURAGED in favor of htmlSaveFileFormat().
 *
 * @param filename  the filename
 * @param cur  the document
 * @param encoding  the document encoding
 * @returns the number of bytes written or -1 in case of failure.
 */
int
htmlSaveFileEnc(const char *filename, xmlDocPtr cur, const char *encoding) {
    return(htmlSaveFileFormat(filename, cur, encoding, 1));
}

#endif /* LIBXML_OUTPUT_ENABLED */

#endif /* LIBXML_HTML_ENABLED */
